﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using EnvDTE;

namespace CodeRazor
{
    class ProjectManager
    {
        private Lazy<_DTE> dteContainer;
        private Lazy<Solution> solutionContainer;

        private ProjectManager(Lazy<_DTE> dteContainer, Lazy<Solution> solutionContainer)
        {
            this.dteContainer = new Lazy<_DTE>(() => CreateActiveDTE());
            this.solutionContainer = new Lazy<Solution>(() => this.dteContainer.Value != null ? this.dteContainer.Value.Solution : null);
        }

        public static ProjectManager FromActiveSolution()
        {
            var dteContainer = new Lazy<_DTE>(() => CreateActiveDTE());
            var solutionContainer = new Lazy<Solution>(() => dteContainer.Value != null ? dteContainer.Value.Solution : null);

            return new ProjectManager(dteContainer, solutionContainer);
        }

        private static ProjectItem FindParent(Project project, FileInfo file, ProjectItem currentProjectItem = null)
        {
            if (currentProjectItem != null)
            {
                string filePath = file.DirectoryName;
                var itemPath = Path.GetFullPath(currentProjectItem.Name);

                if (StringComparer.OrdinalIgnoreCase.Equals(itemPath, filePath))
                {
                    return currentProjectItem;
                }

                foreach (ProjectItem child in currentProjectItem.ProjectItems)
                {
                    ProjectItem parent = FindParent(project, file, child);

                    if (parent != null)
                    {
                        return parent;
                    }
                }
            }

            return null;
        }

        private IEnumerable<ProjectItem> GetProjectItems(Project project, ProjectItem item = null)
        {
            if (item == null)
            {
                foreach (ProjectItem child in project.ProjectItems)
                {
                    foreach (var result in GetProjectItems(project, child))
                    {
                        yield return result;
                    }
                }
            }
            else
            {
                yield return item;

                foreach (ProjectItem child in item.ProjectItems)
                {
                    foreach (var result in GetProjectItems(project, child))
                    {
                        yield return result;
                    }
                }
            }            
        }

        public void AddFileToProject(Project project, FileInfo parentFile, FileInfo outputFile, string contents)
        {
            var allProjectItems = GetProjectItems(project).ToList();
            var parentProjectItem = allProjectItems.FirstOrDefault(pi => pi.Kind == "{6BB5F8EE-4483-11D3-8BCF-00C04F8EC28C}" && StringComparer.OrdinalIgnoreCase.Equals(parentFile.FullName, pi.Properties.Item("FullPath").Value));

            if (parentProjectItem != null)
            {
                string tempFilePath = null;

                try
                {
                    tempFilePath = Path.GetTempFileName();
                    File.WriteAllText(tempFilePath, contents);
                    ProjectItem outputProjectItem = parentProjectItem.ProjectItems.Cast<ProjectItem>().FirstOrDefault(pi=>StringComparer.OrdinalIgnoreCase.Equals(pi.Name, outputFile.Name));

                    if (outputProjectItem != null)
                    {
                        File.Copy(tempFilePath, outputProjectItem.Properties.Item("FullPath").Value, true);
                    }
                    else
                    {
                        outputProjectItem = parentProjectItem.ProjectItems.AddFromTemplate(tempFilePath, outputFile.Name);
                    }
                }
                finally
                {
                    File.Delete(tempFilePath);
                }
            }
        }

        public Project GetActiveProject()
        {
            var dte = this.dteContainer.Value;

            if (dte != null)
            {
                var activeProjects = dte.ActiveSolutionProjects as Array;

                if (activeProjects.Length > 0)
                {
                    var project = activeProjects.GetValue(0) as Project;
                    return project;
                }
            }

            return null;
        }

        public Project GetProjectByName(string projectName)
        {
            var solution = this.solutionContainer.Value;

            if (solution != null)
            {
                foreach (Project currentProject in RetryGet(() => solution.Projects))
                {
                    if (StringComparer.Ordinal.Equals(projectName, currentProject.Name))
                    {
                        return currentProject;
                    }
                }
            }

            return null;
        }

        public IEnumerable<FileInfo> GetFilesInProject(Project project, Func<FileInfo, bool> predicate)
        {
            if (project != null)
            {
                foreach (ProjectItem projectItem in RetryGet(() => project.ProjectItems))
                {
                    foreach (var file in GetFilesInProjectItem(projectItem, predicate))
                    {
                        yield return file;
                    }
                }
            } 
        }

        private IEnumerable<FileInfo> GetFilesInProjectItem(ProjectItem parent, Func<FileInfo, bool> predicate)
        {
            if (parent != null)
            {
                for (short i = 0; i < parent.FileCount; i++)
                {
                    FileInfo file = new FileInfo(parent.FileNames[i]);
                    if (predicate(file))
                    {
                        yield return file;
                    }
                }

                foreach (ProjectItem child in parent.ProjectItems)
                {
                    foreach (var file in GetFilesInProjectItem(child, predicate))
                    {
                        yield return file;
                    }
                }
            }
        }

        private static _DTE CreateActiveDTE()
        {
            var dte = (_DTE)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");
            return dte;
        }

        private static T RetryGet<T>(Func<T> selector, short retryCount = 32, int retryDelayMilliseconds = 1)
        {
            List<Exception> exceptions = null;

            for (short i = 1; i <= retryCount; ++i)
            {
                try
                {
                    return selector();
                }
                catch (System.Runtime.InteropServices.COMException ex)
                {
                    System.Threading.Thread.Sleep(retryDelayMilliseconds);

                    if (exceptions == null)
                    {
                        exceptions = new List<Exception>();
                    }
                    exceptions.Add(ex);
                }
            }

            throw new AggregateException(string.Format("All {0} retry attempts failed", retryCount), exceptions);
        }
    }
}
